// Copyright 2009 Jan Hosang <jan.hosang@gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gomake

import (
    "fmt"
    "os"
    "strings"
    "reflect"
    "log"
    "path"
)

type compilerConfigType struct {
    O, AS, CC, GC, LD string;
    A, PACK string;
}

var builtBinaries = make([]string, 0, 64)
func printBuiltBinaries() {
    if len(builtBinaries) > 0 {
        fmt.Printf("Built executables:\n")
        for _, bin := range builtBinaries {
            fmt.Printf("  %s\n", bin)
        }
    }
}

var compilerConfig compilerConfigType = getCompilerConfig()
func getCompilerConfig() compilerConfigType {
    var defaultConfigs = map[string] compilerConfigType {
        "386":      compilerConfigType{"8", "8a", "8c", "8g", "8l", "a", "gopack"},
        "amd64":    compilerConfigType{"6", "6a", "6c", "6g", "6l", "a", "gopack"},
        "arm":      compilerConfigType{"5", "5a", "5c", "5g", "5l", "a", "gopack"},
    }
    
    arch := os.Getenv("GOARCH")
    config, ok := defaultConfigs[arch]
    if !ok {
        log.Exitf("Architecture \"%s\" unknown. Check your GO environment variable GOARCH.", arch)
    }
    
    bin := os.Getenv("GOBIN")
    if !pathExists(bin) {
        log.Exitf("Could not find \"%s\". Check your GO environment variable GOBIN.", bin)
    }
    config.AS = path.Join(bin, config.AS)
    config.CC = path.Join(bin, config.CC)
    config.GC = path.Join(bin, config.GC)
    config.LD = path.Join(bin, config.LD)
    config.PACK = path.Join(bin, config.PACK)
    return config
}

func (pkg *goPkg) Build() bool {
    objfileDir, _ := path.Split(pkg.objfile)
    os.MkdirAll(objfileDir, 0755)
    exec(compilerConfig.GC, "-I", config.pkg, "-o", pkg.objfile, pkg.srcs)
    
    target := pkg.Output()
    if pkg.name == "main" {
        // main packages just have to be linked
        exec(compilerConfig.LD, "-L", config.pkg, "-o", target, pkg.objfile)
        builtBinaries = append(builtBinaries, target)
    } else {
        // other packges get archived
        targetPath, _ := path.Split(target)
        os.MkdirAll(targetPath, 0755)
        exec(compilerConfig.PACK, "grc", target, pkg.objfile)
    }
    return true
}

func (pkg *goPkg) Changed() bool {
    targetModTime, err := getModTime(pkg.Output())
    if err != nil {
        return true
    }
    return targetModTime < pkg.modTime
}

func getModTime(filename string) (uint64, os.Error) {
    dir, err := os.Stat(filename)
    if err != nil {
        return 0, err
    }
    return dir.Mtime_ns, nil
}

func exec(command ...) {
    v := reflect.NewValue(command).(*reflect.StructValue)
    args := make([]string, 0, 255)
    for i := 0; i < v.NumField(); i += 1 {
        field := v.Field(i).Interface()
        switch field.(type) {
        case string:
            args = append(args, field.(string))
        case []string:
            for _, cmd := range field.([]string) {
                args = append(args, cmd)
            }
        default:
            panic("Unexpected type in arguments.")
        }
    }
    
    fmt.Println(strings.Join(args, " "))
    pid, err := os.ForkExec(args[0], args, os.Environ(), "", []*os.File{os.Stdin, os.Stdout, os.Stderr})
    if err != nil {
        log.Exit(err.String())
    }
    waitMsg, err := os.Wait(pid, 0)
    if err != nil {
        log.Exit(err)
    }
    if waitMsg.WaitStatus != 0 {
        log.Exit(waitMsg)
    }
}

func pathExists(file string) bool {
    dir, err := os.Stat(file)
    if err == nil && (dir.IsDirectory() || dir.IsRegular() || dir.IsSymlink()) {
        return true
    }
    return false
}
